using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace Kopain
{

    /// <summary>This class represents the editor.
    /// </summary>
    internal class NotesTextbox : RichTextBox
    {

#region Classes.

        /// <summary>This class contains the base version of a selection.  A selection is a piece of text with a start and stop.  The length can be calculated from this.  It is not my belief that this should be instantiated but instead inherited from.
        /// </summary>
        private class Selection
        {
            private int _start;
            private int _stop;
            internal int Start{get { return _start; }}
            internal int Stop { get { return _stop; }  }
            internal int Length { get { return _stop - _start+1; } }
            internal Selection( int start, int stop )
            {
                _start = start;
                _stop = stop;
            }
            internal static Selection CreateBySelectionStartAndLength(int start, int length) { return new Selection(start, start + length-1); }
        }

        /// <summary>This class is a list of selections.
        /// </summary>
        private class SelectionList : List<Selection>{}

        /// <summary>This is a Selection class that also contains a flag for Bold.
        /// </summary>
        private class BoldSelection : Selection
        {
            private bool _bold;
            internal bool Bold { get { return _bold; } }
            internal BoldSelection(int start, int stop, bool bold)
                : base(start, stop)
            {
                _bold = bold;
            }
            internal BoldSelection(Selection selection, bool bold) : this(selection.Start, selection.Stop, bold) { }
            [Obsolete("Do not use this consctructor, it is only here for hiding the base class.", true)]
            private BoldSelection(int start, int stop):base(start, stop) { }
        }

        /// <summary>This is a list of selections that might be bold.
        /// </summary>
        private class BoldSelectionList : List<BoldSelection>
        {
            /// <summary>This method returns true if all the selections in the list are Bold.
            /// </summary>
            /// <returns></returns>
            internal bool IsBold()
            {
                foreach (BoldSelection selection in this)
                {
                    if (!selection.Bold) { return false; }  // It is enough with one item to be not bold.
                }
                return true;
            }
        }

        /// <summary>This is a Selection class that also contains a flag for Italic.
        /// </summary>
        private class ItalicSelection : Selection
        {
            private bool _Italic;
            internal bool Italic { get { return _Italic; } }
            internal ItalicSelection(int start, int stop, bool Italic)
                : base(start, stop)
            {
                _Italic = Italic;
            }
            internal ItalicSelection(Selection selection, bool italic) : this(selection.Start, selection.Stop, italic) { }
            [Obsolete("Do not use this consctructor, it is only here for hiding the base class.", true)]
            private ItalicSelection(int start, int stop) : base(start, stop) { }
        }

        /// <summary>This is a list of selections that might be Italic.
        /// </summary>
        private class ItalicSelectionList : List<ItalicSelection>
        {
            /// <summary>This method returns true if all the selections in the list are Italic.
            /// </summary>
            /// <returns></returns>
            internal bool IsItalic()
            {
                foreach (ItalicSelection selection in this)
                {
                    if (!selection.Italic) { return false; }  // It is enough with one item to be not Italic.
                }
                return true;
            }
        }

#endregion   // Classes.

#region Enums.

        /// <summary>This enum contains the types of Navigation that can be fired.
        /// </summary>
        internal enum NavigateTypes
        {
            Up,
            Down,
            Left,
            Right
        }

#endregion  //Enums.

#region Events and Delegates.

        /// <summary>This method is fired when the users wants to navigate from a Note to another without leaving the Note control.
        /// </summary>
        internal event NavigateDelegate OnNavigate;

        /// <summary>This method is fired when the user wants to move to the next panel.  Probablyh the treenavigator.
        /// </summary>
        internal event SetFocusToOtherPanelDelegate OnSetFocusToNextPanel;
        
        /// <summary>This method is fired when the user wants to move to the previous panel.  Probably the treeviewnavigator.
        /// </summary>
        internal event SetFocusToOtherPanelDelegate OnSetFocusToPreviousPanel;
        
        /// <summary>This is a delegate for an event that is fired when the users wants to navigate from a Note to another without leaving the Note control.
        /// </summary>
        /// <param name="navigateType"></param>
        /// <seealso cref="OnNavigate"/>
        internal delegate void NavigateDelegate( NavigateTypes navigateType );

        /// <summary>This is a delegate for OnSetFocusToOtherPanel.
        /// </summary>
        /// <seealso cref="OnsetFocusToNextPanel"/>
        internal delegate void SetFocusToOtherPanelDelegate();

        ///// <summary>This is a delegate for OnSetFocusToPreviousPanel.
        ///// </summary>
        ///// <seealso cref="OnSetFocusToPreviousPanel"/>
        //internal delegate void SetFocusToPreviousControlDelegate();

#endregion  //Events and Delegates.

#region Message sinks.

        /// <summary>This method is not in use for the moment but stays here as a reminder of the heydays.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnKeyPress( KeyPressEventArgs e )
        {
            base.OnKeyPress( e );
        }

        /// <summary>This method catches keypresses and converts stuff like ctrl-B to Bold etc.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnKeyDown( KeyEventArgs e )
        {

            if (Common.CheckKeys(e.KeyData, Keys.Control | Keys.B, true))
            {
                SelectionToggleBold();
                e.SuppressKeyPress = true;
            }
            else if (Common.CheckKeys(e.KeyData, Keys.Control | Keys.I, true))
            {
                SelectionToggleItalic();
                e.SuppressKeyPress = true;
            }

            else if (Common.CheckKeys(e.KeyData, Keys.Alt | Keys.Up, true))
            {
                Raise_OnNavigate(NavigateTypes.Up);
            }
            else if (Common.CheckKeys(e.KeyData, Keys.Alt | Keys.Down, true))
            {
                Raise_OnNavigate(NavigateTypes.Down);
            }
            else if (Common.CheckKeys(e.KeyData, Keys.Alt | Keys.Left, true))
            {
                Raise_OnNavigate(NavigateTypes.Left);
            }
            else if (Common.CheckKeys(e.KeyData, Keys.Alt | Keys.Right, true))
            {
                Raise_OnNavigate(NavigateTypes.Right);
            }

            else if (Common.CheckKeys(e.KeyData, Keys.F6, true))
            {
                Raise_SetFocusToPreviousPanel();
            }
            else if (Common.CheckKeys(e.KeyData, Keys.Shift | Keys.F6, true))
            {
                Raise_SetFocusToNextPanel();
            }

            base.OnKeyDown(e);

            //if( e.Control )
            //{
            //    switch( e.KeyCode )
            //    {
            //        case Keys.B:
            //            SelectionToggleBold();
            //            e.SuppressKeyPress = true;
            //            break;
            //        case Keys.I:
            //            SelectionToggleItalic();
            //            e.SuppressKeyPress = true;
            //            break;
            //        default:
            //            base.OnKeyDown( e );
            //            break;
            //    }
            //}
            //else if (e.Alt)
            //{
            //    switch (e.KeyCode)
            //    {
            //        case Keys.Up:
            //            Raise_OnNavigate(NavigateTypes.Up);
            //            break;
            //        case Keys.Down:
            //            Raise_OnNavigate(NavigateTypes.Down);
            //            break;
            //        case Keys.Left:
            //            Raise_OnNavigate(NavigateTypes.Left);
            //            break;
            //        case Keys.Right:
            //            Raise_OnNavigate(NavigateTypes.Right);
            //            break;
            //        default:
            //            base.OnKeyDown(e);
            //            break;
            //    }
            //}
            //else if (e.Shift)
            //{
            //    switch (e.KeyCode)
            //    {
            //        case Keys.Tab:
            //            Raise_SetFocusToPreviousControl();
            //            break;
            //        default:
            //            base.OnKeyDown(e);
            //            break;
            //    }
            //}
            //else
            //{
            //    base.OnKeyDown(e);
            //}
        }
        
#endregion  //Message sinks.

#region Private methods.

        /// <summary>This method toggles a selection to and fro Bold.
        /// </summary>
        private void SelectionToggleBold()
        {
            Selection originalSelection = Selection.CreateBySelectionStartAndLength(SelectionStart, SelectionLength );

            BoldSelectionList boldSelectionList = new BoldSelectionList();
            foreach (Selection selection in SplitSelection(SelectionStart, SelectionLength))
            {
                Select( selection.Start, selection.Length );
                boldSelectionList.Add(new BoldSelection(selection, SelectionFont.Bold));
            }

            bool setToBold = !boldSelectionList.IsBold();
            foreach (BoldSelection selection in boldSelectionList)
            {
                SetBold(selection.Start, selection.Length, setToBold);
            }

            Select( originalSelection.Start, originalSelection.Length );

            #region Olde code.
            
            //// Store the original selection.
            ////
            //int originalStart = SelectionStart;
            //int originalLength = SelectionLength;
            //int originalStop = originalStart + originalLength;

            //System.Diagnostics.Debug.Assert(0 != originalLength, "Something must be chosen or bail out..  Havn't decided yet.");

            //// The problem is if there are several fonts in the same selected text.  Do a check and if it is; do lots of stuff.  Otherwise just toggle the selection.
            //if (null == SelectionFont)
            //{   // There are several fonts so split.

            //    // The loop variables must be set to something because they are used outside the loop.
            //    int start = originalStart;
            //    int stop = originalStop;
            //    BoldSelectionList selectionList = new BoldSelectionList();

            //    // Loop from start to stop.  For every part with one and only one font we create a Selection.
            //    for (stop = start + 1; stop <= originalStop; ++stop)
            //    {

            //        Select(start, stop - start);
            //        if (null == SelectionFont)
            //        {
            //            // There is more than 1 font so add the start/length for the last part of the string (the loop before this).
            //            Select(start, stop- start- 1);
            //            selectionList.Add(new BoldSelection(start, stop - 2, SelectionFont.Bold));

            //            // Move the start point to where we are now and continue.
            //            start = stop - 1;
            //            stop = start; // 1 will be added in the next round.
            //        }

            //    }
            //    // Add the last string part.
            //    //
            //    selectionList.Add(new BoldSelection( start, stop - 1, SelectionFont.Bold));

            //    // Now find out if the all strings are bold, not bold or a mix.  Or rather - we only care if they all are bold because that is the only time we unbold them.
            //    bool toBold = !selectionList.IsBold();

            //    // Do the toggling..
            //    foreach (Selection selection in selectionList)
            //    {
            //        SwitchBold(selection.Start, selection.Length, toBold);
            //    }

            //    // Reselect the originally selected text.
            //    Select(originalStart, originalLength);
            //}
            //else
            //{   // There is only one font so go ahead and toggle bold.
            //    ToggleBold(originalStart, originalLength);
            //}

            #endregion   // Olde code.

        }

        /// <summary>This method toggles a selection to and fro Italic.
        /// </summary>
        private void SelectionToggleItalic()
        {
            Selection originalSelection = Selection.CreateBySelectionStartAndLength(SelectionStart, SelectionLength);

            ItalicSelectionList ItalicSelectionList = new ItalicSelectionList();
            foreach (Selection selection in SplitSelection(SelectionStart, SelectionLength))
            {
                Select(selection.Start, selection.Length);
                ItalicSelectionList.Add(new ItalicSelection(selection, SelectionFont.Italic));
            }

            bool setToItalic = !ItalicSelectionList.IsItalic();
            foreach (ItalicSelection selection in ItalicSelectionList)
            {
                SetItalic(selection.Start, selection.Length, setToItalic);
            }

            Select(originalSelection.Start, originalSelection.Length);

#region Olde code.

        ///// <summary>This method toggles a selection to and fro Italic.
        ///// </summary>
        //private void SelectionToggleItalic()
        //{
        //    if( null != SelectionFont )
        //    {
        //        System.Drawing.Font currentFont = SelectionFont;
        //        System.Drawing.FontStyle newFont =
        //            (currentFont.Italic
        //            ? currentFont.Style & ~System.Drawing.FontStyle.Italic
        //            : currentFont.Style | System.Drawing.FontStyle.Italic);
        //        SelectionFont = new System.Drawing.Font( currentFont.FontFamily, currentFont.Size, newFont );
        //    }
        //}

	#endregion   // Olde code.

        }

        /// <summary>This method switches the Bold status of the text between the parameters.  Note: The text is selected too so if there was an original selection - do not forget to restore it.
        /// </summary>
        /// <param name="start"></param>
        /// <param name="length"></param>
        /// <param name="bold"></param>
        private void SetBold(int start, int length, bool bold)
        {
            Select(start, length);
            System.Drawing.Font currentFont = SelectionFont;
            System.Drawing.FontStyle newFont =
                (bold
                ? currentFont.Style | System.Drawing.FontStyle.Bold
                : currentFont.Style & ~System.Drawing.FontStyle.Bold);
            SelectionFont = new System.Drawing.Font(currentFont.FontFamily, currentFont.Size, newFont);
        }

        /// <summary>This method switches the Italic status of the text between the parameters.  Note: The text is selected too so if there was an original selection - do not forget to restore it.
        /// </summary>
        /// <param name="start"></param>
        /// <param name="length"></param>
        /// <param name="Italic"></param>
        private void SetItalic(int start, int length, bool Italic)
        {
            Select(start, length);
            System.Drawing.Font currentFont = SelectionFont;
            System.Drawing.FontStyle newFont =
                (Italic
                ? currentFont.Style | System.Drawing.FontStyle.Italic
                : currentFont.Style & ~System.Drawing.FontStyle.Italic);
            SelectionFont = new System.Drawing.Font(currentFont.FontFamily, currentFont.Size, newFont);
        }

        /// <summary>This method returns a list of parts of a string splitted on different fonts.  Each item in the result, Selection, holds one and only one font.
        /// </summary>
        /// <param name="start"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        private SelectionList SplitSelection (int start, int length)
        {
            // Store the original selection.
            //
            int originalSelectionStart = SelectionStart;
            int originalSelectionLength = SelectionLength;

            int stop = start + length-1;

            System.Diagnostics.Debug.Assert(0 != length, "Something must be chosen or bail out..  Havn't decided yet.");

            SelectionList ret = new SelectionList();

            // The problem is if there are several fonts in the same selected text.  Do a check and if it is; do lots of stuff.  Otherwise just toggle the selection.
            Select(start, length);
            if (null == SelectionFont)
            {   // There are several fonts so split.

                // The loop variables must be set to something because they are used outside the loop.
                int newStart = start;
                int newStop = stop;

                // Loop from start to stop.  For every part with one and only one font we create a Selection.
                for (newStop = newStart + 1; newStop <= stop; ++newStop)
                {
                    Select(newStart, newStop - newStart);
                    if (null == SelectionFont)
                    {
                        // There is more than 1 font so add the start/length for the last part of the string (the loop before this).
                        Select(newStart, newStop - newStart - 1);
                        ret.Add(new Selection(newStart, newStop - 2));

                        // Move the start point to where we are now and continue.
                        newStart = newStop - 1;
                        newStop = newStart; // 1 will be added in the next round.
                    }

                }
                // Add the last string part.
                //
                ret.Add(new Selection(newStart, newStop - 1));

            }
            else
            {   // There is only one font so go ahead and toggle bold.
                ret.Add(new Selection(start, stop));
            }

            // Reselect the originally selected text.
            Select(originalSelectionStart, originalSelectionLength);

            return ret;
        }

        /// <summary>This method toggles the Bold status of the text between the parameters.  Note: The text is selected too so if there was an original selection - do not forget to restore it.
        /// </summary>
        /// <param name="start"></param>
        /// <param name="length"></param>
        private void ToggleBold(int start, int length)
        {
            Select(start, length);
            System.Drawing.Font currentFont = SelectionFont;
            System.Drawing.FontStyle newFont =
                (currentFont.Bold
                ? currentFont.Style & ~System.Drawing.FontStyle.Bold
                : currentFont.Style | System.Drawing.FontStyle.Bold);
            SelectionFont = new System.Drawing.Font(currentFont.FontFamily, currentFont.Size, newFont);
        }

        /// <summary>This method toggles a selection to and from Italic.. Note: The text is selected too so if there was an original selection - do not forget to restore it.
        /// </summary>
        /// <param name="start"></param>
        /// <param name="length"></param>
        private void ToggleItalic( int start, int length)
        {
            if (null != SelectionFont)
            {
                Select(start, length);
                System.Drawing.Font currentFont = SelectionFont;
                System.Drawing.FontStyle newFont =
                    (currentFont.Italic
                    ? currentFont.Style & ~System.Drawing.FontStyle.Italic
                    : currentFont.Style | System.Drawing.FontStyle.Italic);
                SelectionFont = new System.Drawing.Font(currentFont.FontFamily, currentFont.Size, newFont);
            }
        }

#region Event raising methods.

        /// <summary>This is a helper method to raise an event if there is such.
        /// </summary>
        /// <param name="navigateType"></param>
        private void Raise_OnNavigate(NavigateTypes navigateType)
        {
            NavigateDelegate tempEvent = OnNavigate;
            if (null != tempEvent)
            {
                OnNavigate(navigateType);
            }
        }

        /// <summary>This is a helper method to raise an event if there is such.
        /// </summary>
        private void Raise_SetFocusToNextPanel()
        {
            SetFocusToOtherPanelDelegate tempEvent = OnSetFocusToNextPanel;
            if (null != tempEvent)
            {
                OnSetFocusToNextPanel();
            }
        }

        /// <summary>This is a helper method to raise an event if there is such.
        /// </summary>
        private void Raise_SetFocusToPreviousPanel()
        {
            SetFocusToOtherPanelDelegate tempEvent = OnSetFocusToPreviousPanel;
            if (null != tempEvent)
            {
                OnSetFocusToPreviousPanel();
            }
        }

#endregion  //  Event raising methods.

#endregion  //Private methods.

    }

}
